/* Copyright (c) 2007, Jan Flora <janflora@diku.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the University of Copenhagen nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
  @author Jan Flora <janflora@diku.dk>
*/

/* This module provides 32-bit 802.15.4 2.4 GHz symbol time for Tiny15Four.
   Since the HCS08 only provides 16-bit hardware timers some virtualization is needed.

   NB: This module expects the MCU to be driven by a 62.5 KHz external clock signal in FEE mode,
       making XCLK = 32 KHz.
*/

#include <time.h>

module HPLTimer2M
{
	provides
	{
		interface StdControl;
		interface SymbolCounter<time_t> as SymbolCounter;
		interface SymbolChannel<time_t> as SymbolChannel;
	}
}
implementation
{
	#define NUM_CHANNELS 5
	uint16_t wrapCounter = 0;

	typedef struct {
		uint8_t id;
		bool free;
		bool pending;
		uint16_t highStamp;
		uint16_t lowStamp;
	} Channel_t;
	
	Channel_t virtualChannels[NUM_CHANNELS-1];

	// Forward declarations
	result_t armHwChannel(uint16_t timeStamp, uint8_t channel);
	result_t disableHwChannel(uint8_t channel);
	result_t programVirtualChannel(uint16_t highStamp, uint16_t lowStamp, uint8_t channel);
	void fireVirtualChannel(uint8_t channel);

	command result_t StdControl.init()
	{
		uint8_t i;
		
		// Initialize timer ports in output compare mode with software compare only.
		TPM2C0SC = 0x10;
		TPM2C1SC = 0x10;
		TPM2C2SC = 0x10;
		TPM2C3SC = 0x10;
		TPM2C4SC = 0x10;
		
		// Last channel is reserved for virtualization control.
		for (i=0;i<(NUM_CHANNELS-1);i++) {
			atomic {
				virtualChannels[i].pending = FALSE;
				virtualChannels[i].free = TRUE;
			}
		}
		return SUCCESS;
	}

	command result_t StdControl.start()
	{
		// Enable overflow interrupts.
		TPM2SC_TOIE = 1;
		// Using XCLK.
		TPM2SC |= 0x10;
		
		// Start up timer 4.
		armHwChannel(0x7FFF, 4);
		return SUCCESS;
	}

	command result_t StdControl.stop()
	{
		//no clock, disabled
		TPM2SC &= (~0x58);
		return SUCCESS;
	}

	/***************************************
	 * HCS08 SymbolCounter implementation.
	 **************************************/
	async command uint32_t SymbolCounter.getTime()
	{
		uint32_t time = 0;
		atomic time = wrapCounter;
		time <<= 16;
		time += TPM2CNT;
		// Shift to get symbol time.
		return time<<1;
	}
	
	async command void SymbolCounter.reset()
	{
		// Reset the counter.
		atomic wrapCounter = 0;
		TPM2CNT = 0;
	}

	/***************************************
	 * HCS08 SymbolChannel implementation.
	 **************************************/
	async command result_t SymbolChannel.arm(uint8_t id, uint32_t timeStamp)
	{
		uint8_t i;
		uint8_t myChannel;
		bool wasFree = FALSE;
		
		// Find an available channel.
		for (i=0; i < (NUM_CHANNELS-1); i++) {
			atomic {
				wasFree = virtualChannels[i].free;
				virtualChannels[i].free = FALSE;
			}
			if (wasFree) {
				// Program the virtual channel.
				virtualChannels[i].id = id;
				programVirtualChannel(timeStamp>>16, timeStamp&0xFFFF, i);
				return SUCCESS;
			}
		}
		// No available channels.
		return FAIL;
	}

	async command result_t SymbolChannel.stop(uint8_t id)
	{
		uint8_t i;
		atomic {
			// Find channel associated with the identifier.
			for (i=0; i < (NUM_CHANNELS-1); i++) {
				if (virtualChannels[i].id == id) {
					// Free virtual channel and stop hardware channel.
					disableHwChannel(i);
					virtualChannels[i].pending = FALSE;
					atomic virtualChannels[i].free = TRUE;
				}
			}
		}
	}


	/***************************************
	 * Virtualization interrupt handlers.
	 **************************************/
	TOSH_SIGNAL(TPM2OVF)
	{
		// Clear the interrupt flag
		TPM2SC &= 0x7F;
		// Increment the wrap counter
		atomic wrapCounter++;
		
		// Handle channel virtualization.
		atomic {
			uint8_t i;
			uint16_t nowHigh = (call SymbolCounter.getTime())>>16;
			for (i = 0; i < (NUM_CHANNELS-1); i++) {
				Channel_t* chan = virtualChannels+i;
				if (chan->pending) {
					if (chan->highStamp == nowHigh && chan->lowStamp > 0x7FFF) {
						chan->pending = FALSE;
						armHwChannel(chan->lowStamp, i);
					}
				}
			}
		}
	}

	TOSH_SIGNAL(TPM2CH4)
	{
		// Clear the interrupt flag
		TPM2C4SC &= 0x7F;
		
		// Handle channel virtualization.
		atomic {
			uint8_t timer;
			uint8_t i;
			uint16_t nowHigh = ((call SymbolCounter.getTime())>>16)+1;
			for (i = 0; i < (NUM_CHANNELS-1); i++) {
				Channel_t* chan = virtualChannels+i;
				if (chan->pending) {
					if (chan->highStamp == nowHigh && chan->lowStamp <= 0x7FFF) {
						chan->pending = FALSE;
						armHwChannel(chan->lowStamp, i);
					}
				}
			}
		}
	}

	/***************************************
	 * Channel interrupt handlers.
	 **************************************/
	TOSH_SIGNAL(TPM2CH0)
	{
		// Clear the interrupt flag
		TPM2C0SC &= 0x7F;
		fireVirtualChannel(0);
	}

	TOSH_SIGNAL(TPM2CH1)
	{
		// Clear the interrupt flag
		TPM2C1SC &= 0x7F;
		fireVirtualChannel(1);
	}

	TOSH_SIGNAL(TPM2CH2)
	{
		// Clear the interrupt flag
		TPM2C2SC &= 0x7F;
		fireVirtualChannel(2);
	}
	
	TOSH_SIGNAL(TPM2CH3)
	{
		// Clear the interrupt flag
		TPM2C3SC &= 0x7F;
		fireVirtualChannel(3);
	}


	/***************************************
	 * Helper functions.
	 **************************************/
	result_t armHwChannel(uint16_t timeStamp, uint8_t channel)
	{
		if (channel == 0) {
			TPM2C0SC &= 0x7F;
			TPM2C0V = timeStamp;
			TPM2C0SC_CH0IE = 1;
			return SUCCESS;
		} else if (channel == 1) {
			TPM2C1SC &= 0x7F;
			TPM2C1V = timeStamp;
			TPM2C1SC_CH1IE = 1;
			return SUCCESS;		
		} else if (channel == 2) {
			TPM2C2SC &= 0x7F;
			TPM2C2V = timeStamp;
			TPM2C2SC_CH2IE = 1;
			return SUCCESS;
		} else if (channel == 3) {
			TPM2C3SC &= 0x7F;
			TPM2C3V = timeStamp;
			TPM2C3SC_CH3IE = 1;
			return SUCCESS;
		} else if (channel == 4) {
			TPM2C4SC &= 0x7F;
			TPM2C4V = timeStamp;
			TPM2C4SC_CH4IE = 1;
			return SUCCESS;
		}
		return FAIL;
	}
	
	result_t disableHwChannel(uint8_t channel)
	{
		if (channel == 0) {
			TPM2C0SC_CH0IE = 0;
			return SUCCESS;
		} else if (channel == 1) {
			TPM2C1SC_CH1IE = 0;
			return SUCCESS;		
		} else if (channel == 2) {
			TPM2C2SC_CH2IE = 0;
			return SUCCESS;
		} else if (channel == 3) {
			TPM2C3SC_CH3IE = 0;
			return SUCCESS;
		}
		return FAIL;
	}
	
	result_t programVirtualChannel(uint16_t highStamp, uint16_t lowStamp, uint8_t channel)
	{
		atomic {
			uint32_t myTime = call SymbolCounter.getTime();
			uint16_t nowHigh = myTime>>16;
			uint16_t nowLow = myTime&0xFFFF;
			if ((highStamp == nowHigh+1 && (lowStamp > nowLow)) || highStamp > nowHigh+1) {
				virtualChannels[channel].pending = TRUE;
				virtualChannels[channel].highStamp = highStamp;
				virtualChannels[channel].lowStamp = lowStamp;
				return SUCCESS;
			} else {
				// Program hw timer immediately.
				return armHwChannel(lowStamp, channel);
			}
		}
	}

	void fireVirtualChannel(uint8_t channel)
	{
		uint8_t myId = virtualChannels[channel].id;
		disableHwChannel(channel);
		atomic virtualChannels[channel].free = TRUE;
		signal SymbolChannel.fired(myId);
	}
	
	default async event void SymbolChannel.fired(uint8_t channel)
	{
	}

}
